package com.zzy.factory;

import com.mysql.jdbc.StringUtils;
import com.zzy.annotation.*;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.dom4j.tree.DefaultElement;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class BeanFactory {

    private static Map<String,Object> map = new HashMap<>();  // 存储对象

    private static Map<String, Object> singletonFactories = new HashMap<>();

    private static Map<String, Object> earlySingletonObjects = new HashMap<>();


    static {
        // 加载xml
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
        // 解析xml
        SAXReader saxReader = new SAXReader();
        try {
            Document document = saxReader.read(resourceAsStream);
            Element rootElement = document.getRootElement();
            DefaultElement scanNode = (DefaultElement) rootElement.selectSingleNode("//component-scan");
            // 获取扫描包路径
            String basePackage = scanNode.attributeValue("base-package");

            List<String> classNames = doScan(basePackage);
            for (String path : classNames) {
                Class<?> clazz = Class.forName(path);
                // 是否包含Component注解
                if (!clazz.isAnnotation() && hasAnnotationPresent(clazz, Component.class)){
                    String value = null;
                    Component component = clazz.getAnnotation(Component.class);
                    if (component == null) {
                        Service service = clazz.getAnnotation(Service.class);
                        if (service == null) {
                            Repository repository = clazz.getAnnotation(Repository.class);
                            if (repository != null) {
                                value = repository.value();
                            }
                        } else {
                            value = service.value();
                        }
                    } else {
                        value = component.value();
                    }
                    String beanName = clazz.getSimpleName().substring(0,1).toLowerCase() + clazz.getSimpleName().substring(1);
                    if (!StringUtils.isNullOrEmpty(value)) {
                        beanName = value;
                    }
                    createBean(clazz, beanName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    protected static String getPackagePath(String packageName) {
        return packageName == null ? null : packageName.replace('.', '/');
    }

    private static List<String> doScan(String basePackage) throws IOException {
        List<String> urls = new ArrayList<>();
        String path = getPackagePath(basePackage);
        URL url = Thread.currentThread().getContextClassLoader().getResource(path);
        File files = new File(url.getFile());
        isDirectory(files, urls, basePackage);
        return urls;
    }

    private static void isDirectory(File f, List<String> urls, String basePackage) {
        for (File file : f.listFiles()) {
            StringBuilder sb = new StringBuilder(basePackage);
            if (file.isDirectory()) {
                sb.append(".");
                sb.append(file.getName());
                isDirectory(file, urls, sb.toString());
            }else if (file.getAbsolutePath().endsWith(".class")) {
                sb.append(".");
                sb.append(file.getName().substring(0, file.getName().indexOf(".class")));
                urls.add(sb.toString());
            }
        }

    }

    private static List<URL> getResources(String path) throws IOException {
        return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
    }

    private static Object createBean(Class<?> clazz, String beanName) throws Exception {
        if (getBean(beanName)!=null) {
            return getBean(beanName);
        }
        Object newInstance = clazz.getDeclaredConstructor().newInstance();
        // 加入三级缓存
        if (!singletonFactories.containsKey(beanName)) {
            singletonFactories.put(beanName, newInstance);
            earlySingletonObjects.remove(beanName);
        }
        buildFields(clazz, newInstance);
        if (clazz.isAnnotationPresent(Service.class)) {
            for (Method method : clazz.getMethods()) {
                if (method.isAnnotationPresent(Transactional.class)) {
                    ProxyFactory proxyFactory = (ProxyFactory)getBean("proxyFactory");
                    if (proxyFactory == null) {
                        proxyFactory = (ProxyFactory)createBean(ProxyFactory.class, "proxyFactory");
                    }
                    // 如果没实现接口，则采用cglib动态代理，否则，使用jdk动态代理
                    if (clazz.getInterfaces().length == 0) {
                        newInstance = proxyFactory.getCglibProxy(newInstance);
                    }else {
                        newInstance = proxyFactory.getJdkProxy(newInstance);
                    }
                }
            }
        }
        singletonFactories.remove(beanName);
        earlySingletonObjects.remove(beanName);
        map.put(beanName, newInstance);
        return newInstance;
    }

    private static void buildFields(Class<?> clazz, Object newInstance) throws Exception {
        for (Field field : clazz.getDeclaredFields()) {
            // 是否包含Autowired注解
            if (field.isAnnotationPresent(Autowired.class)) {
                Autowired annotation = field.getAnnotation(Autowired.class);
                Class<?> type = field.getType();
                String typeName = type.getSimpleName().substring(0,1).toLowerCase() + type.getSimpleName().substring(1);
                if (!StringUtils.isNullOrEmpty(annotation.name())) {
                    typeName = annotation.name();
                }
                Object bean = getBean(typeName);
                if (bean == null) {
                    bean = createBean(type, typeName);
                }
                field.setAccessible(true);
                field.set(newInstance, bean);
            }
        }
    }

    public static Object getBean(String beanName) {
        Object object = map.get(beanName);
        if (object == null) {
            object = earlySingletonObjects.get(beanName);
            if (object == null) {
                object = singletonFactories.get(beanName);
                if (object!=null) {
                    earlySingletonObjects.put(beanName, object);
                    singletonFactories.remove(beanName);
                }
            }
        }
        return object;
    }

    public static boolean hasAnnotationPresent(Class<?> clazz, Class<?> annotationClazz) {
        for (Annotation annotation : clazz.getAnnotations()) {
            if (annotation.annotationType() != Deprecated.class &&
                    annotation.annotationType() != SuppressWarnings.class &&
                    annotation.annotationType() != Override.class &&
                    annotation.annotationType() != Target.class &&
                    annotation.annotationType() != Retention.class &&
                    annotation.annotationType() != Documented.class &&
                    annotation.annotationType() != Inherited.class
            ) {
                if (annotation.annotationType() == annotationClazz) {
                    return true;
                } else {
                    return hasAnnotationPresent(annotation.annotationType(), annotationClazz);
                }
            }
        }
        return false;
    }
}
